package main

import (
	"encoding/json"
	"fmt"
	"github.com/go-ini/ini"
	"github.com/go-resty/resty/v2"
	"io"
	"log"
	"os"
	"path/filepath"
	"time"
)

// 错误处理函数
func checkErr(err error) {
	if err != nil {
		log.Println(err)
		panic(err)
	}
}

type AppConfig struct {
	SourceNacosUrl      string `ini:"source.nacos.url"`
	SourceNacosUsername string `ini:"source.nacos.username"`
	SourceNacosPassword string `ini:"source.nacos.password"`
	DestNacosUrl        string `ini:"dest.nacos.url"`
	DestNacosUsername   string `ini:"dest.nacos.username"`
	DestNacosPassword   string `ini:"dest.nacos.password"`
}

func main() {
	//记录日志
	f, err := os.OpenFile("main.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
	if err != nil {
		return
	}
	defer func() {
		f.Close()
	}()

	// 组合一下即可，os.Stdout代表标准输出流
	multiWriter := io.MultiWriter(os.Stdout, f)
	log.SetOutput(multiWriter)
	log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)

	config, err := ini.Load("config.ini")
	checkErr(err)
	//读取并映射到结构体中
	var appConfig = new(AppConfig)
	err = config.MapTo(appConfig)
	log.Printf("配置信息:%s", appConfig)
	if err != nil {
		fmt.Printf("映射配置信息失败 %v", err)
		return
	}

	currentTime := time.Now()
	dirName := currentTime.Format("20060102150405")

	client, response, err, results, done := login(appConfig.SourceNacosUrl, appConfig.SourceNacosUsername, appConfig.SourceNacosPassword, err)
	if done {
		return
	}
	accessToken, _ := results["accessToken"].(string)

	response, err, namespaces, done2 := getAllNamespace(appConfig.SourceNacosUrl, accessToken, response, err, client)
	if done2 {
		return
	}

	log.Println(namespaces["data"])

	configPath := fmt.Sprintf("%s/nacos/v1/cs/configs", appConfig.SourceNacosUrl)

	err = os.Mkdir(dirName, os.ModeDir)
	_ = os.Chmod(dirName, 0777)

	if err != nil {
		log.Println(err)
		return
	}

	withFile, err := os.Create(filepath.Join(dirName, "n.json"))
	if err != nil {
		log.Println(err)
		return
	}
	defer withFile.Close()

	json.NewEncoder(withFile).Encode(namespaces["data"])

	configsData := namespaces["data"].([]interface{})

	for _, tenantInfo := range configsData {
		tenantInfoMap := tenantInfo.(map[string]interface{})
		params := map[string]string{
			"search":   "accurate",
			"tenant":   tenantInfoMap["namespace"].(string),
			"group":    "",
			"dataId":   "",
			"pageNo":   "1",
			"pageSize": "1000",
		}

		response, err = client.R().
			SetQueryParams(params).
			Get(configPath)
		if err != nil {
			log.Println(err)
			continue
		}

		tenantDir := filepath.Join(dirName, tenantInfoMap["namespaceShowName"].(string))
		err = os.Mkdir(tenantDir, os.ModeDir)
		_ = os.Chmod(tenantDir, 0777)
		if err != nil {
			log.Println(err)
			continue
		}

		var results map[string]interface{}
		err = json.Unmarshal(response.Body(), &results)
		if err != nil {
			log.Println(err)
			continue
		}

		for _, item := range results["pageItems"].([]interface{}) {
			itemMap := item.(map[string]interface{})
			content := itemMap["content"].(string)
			dataID := itemMap["dataId"].(string)
			withFile, err := os.Create(filepath.Join(tenantDir, dataID))
			_ = os.Chmod(filepath.Join(tenantDir, dataID), 0777)
			if err != nil {
				log.Println(err)
				continue
			}
			defer withFile.Close()

			withFile.Write([]byte(content))
		}

	}

	log.Println("导出完成")

	nacosURLNew := appConfig.DestNacosUrl
	newNS := fmt.Sprintf("%s/nacos/v1/console/namespaces", nacosURLNew)
	newCF := fmt.Sprintf("%s/nacos/v1/cs/configs", nacosURLNew)

	client, response, err, results, done2 = login(appConfig.DestNacosUrl, appConfig.DestNacosUsername, appConfig.DestNacosPassword, err)
	if done2 {
		return
	}
	accessToken, _ = results["accessToken"].(string)
	getFileNames := func(directory string) []string {
		fileNames := []string{}
		items, err := os.ReadDir(directory)
		if err != nil {
			log.Println(err)
			return fileNames
		}
		for _, item := range items {
			if item.IsDir() {
				continue
			}
			fileNames = append(fileNames, item.Name())
		}
		return fileNames
	}

	for _, tenantInfo := range configsData {
		tenantInfoMap := tenantInfo.(map[string]interface{})
		if tenantInfoMap["namespace"] != "" {
			data := map[string]string{"customNamespaceId": tenantInfoMap["namespace"].(string), "namespaceName": tenantInfoMap["namespaceShowName"].(string), "accessToken": accessToken}
			_, err := client.R().
				SetFormData(data).
				Post(newNS)
			if err != nil {
				log.Println(err)
				continue
			}
		}

		files := getFileNames(filepath.Join(dirName, tenantInfoMap["namespaceShowName"].(string)))
		headers := map[string]string{"Content-Type": "application/x-www-form-urlencoded"}
		data := map[string]string{"tenant": tenantInfoMap["namespace"].(string), "group": "DEFAULT_GROUP", "type": "yaml"}

		for _, file := range files {
			log.Println(file)
			data["dataId"] = file
			content, err := os.ReadFile(filepath.Join(dirName, tenantInfoMap["namespaceShowName"].(string), file))
			if err != nil {
				log.Println(err)
				continue
			}
			data["content"] = string(content)
			data["accessToken"] = accessToken

			response, err = client.R().
				SetHeaders(headers).
				SetFormData(data).
				Post(newCF)
			if err != nil {
				log.Println(err)
				continue
			}
			//成功是返回字符串"true",失败解析json格式里面的status
			if string(response.Body()) != "true" {
				log.Println("导入目的nacos失败，失败原因:", string(response.Body()))
				return
			}
			log.Println(response)
		}
	}
	log.Println("执行完毕，退出")

}

func getAllNamespace(url string, accessToken string, response *resty.Response, err error, client *resty.Client) (*resty.Response, error, map[string]interface{}, bool) {
	namespacesPath := fmt.Sprintf("%s/nacos/v1/console/namespaces?accessToken=%s", url, accessToken)
	response, err = client.R().
		Get(namespacesPath)
	if err != nil {
		log.Println(err)
		return nil, nil, nil, true
	}

	var namespaces map[string]interface{}
	err = json.Unmarshal(response.Body(), &namespaces)
	if err != nil {
		log.Println(err)
		return nil, nil, nil, true
	}
	return response, err, namespaces, false
}

func login(url string, username string, password string, err error) (*resty.Client, *resty.Response, error, map[string]interface{}, bool) {
	loginPath := fmt.Sprintf("%s/nacos/v1/auth/users/login?message=true", url)
	data := map[string]string{"username": username, "password": password}

	headers := map[string]string{"Content-Type": "application/x-www-form-urlencoded"}
	// Create a Resty client
	client := resty.New()
	response, err := client.R().
		SetFormData(data).
		SetHeaders(headers).
		Post(loginPath)
	if err != nil {
		log.Println(err)
		return nil, nil, nil, nil, true
	}
	if response.RawResponse.StatusCode != 200 {
		log.Printf("连接nacos失败,连接地址：%s,失败原因：%s", loginPath, response.RawResponse.Status)
		return nil, nil, nil, nil, true
	}

	var results map[string]interface{}
	err = json.Unmarshal(response.Body(), &results)
	if err != nil {
		log.Printf("调用源nacos失败%s", err)
		return nil, nil, nil, nil, true
	}

	log.Println(results)
	return client, response, err, results, false
}
